<!DOCTYPE html>
<html>
  <style></style>
  <body>
    <div id="app"></div>
  </body>
  <script type="text/javascript">
    /**
     * 内置组件翻译器
     */
    class BuiltInComponentTranslator {
      render(component) {
        throw new Error('render method not implements ... ');
      }
    }

    class TextTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // text 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('span');
        }
        element.innerText = component.text;
        return element;
      }
    }

    class ImageTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // image 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('img');
        }
        element.src = component.src;
        return element;
      }
    }

    class BaseContainerTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // image 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('div');
        }
        component.children.forEach((child, index) => {
          const childElement = child.render();
          element.appendChild(childElement);
        });
        return element;
      }
    }

    /**
     * 表格渲染转换器
     */
    class TableTranslator extends BuiltInComponentTranslator {
      render(component) {
        let element = component._renderElement;
        if (!element) {
          // image 元素如果组件已经渲染则直接更新组件属性即可
          element = document.createElement('table');
        }
        // component.children.forEach((child, index) => {
        //   const childElement = child.render();
        //   element.appendChild(childElement);
        // });
        return element;
      }
    }

    const BuiltInBaseContainerModel = {
      // 容器
      name: 'BaseContainer',
      props: [],
      layoutSlots: [
        {
          name: 'children',
          type: 'List<ComponentModel>',
          isContainer: true,
          requried: false,
          scope: 'public',
          default: true,
        },
      ],
      layout: [
        {
          loop: {
            source: '${slot/children}',
            indexName: 'index',
            valueName: 'child',
          },
          condition: '',
          content: 'child',
        },
      ],
    };

    const BuiltInBaseContainerModelXml = `
      <BaseContainer>
        <slots>
          <children type="ComponentModel[]" container requried default/>
        </slots>
        <layout>
          <loop souce='slot/children' indexName='index' valuenName='child'>
            <child />
          </loop>
        </layout>
      </BaseContainer>
    `;

    const BuiltInTextModel = {
      // 文字
      name: 'Text',
      props: [
        {
          value: {
            type: 'Object',
            requried: false,
            editable: true,
            scope: 'public',
          },
        },
      ],
      layout: [
        {
          condition: '',
          isHtml: true,
          content: 'span',
          dataBind: {
            innerText: '${/value}',
          },
        },
      ],
    };

    // f_ 表示 false_xxxx , 即表示某个属性为 false
    const BuiltInTextModelXml = `
      <Text>
        <props>
          <value type="Object" f_requried editable scope="public" />
        </props>
        <layout>
          <translator type='TextTranslator'/>
        </layout>
      </Text>
    `;

    const BuiltInImageModel = {
      // 图片
      name: 'Image',
      props: [
        {
          src: {
            type: 'String',
            requried: false,
            editable: true,
            scope: 'public',
          },
        },
      ],
      layout: [
        {
          condition: '',
          isHtml: true,
          content: 'img',
          dataBind: {
            src: '${/src}',
          },
        },
      ],
    };

    const BuiltInImageModelXml = `
      <Image>
        <props>
          <src type="Object" f_requried editable scope="public" />
        </props>
        <layout>
          <translator type='ImageTranslator'/>
        </layout>
      </Image>
    `;

    // 表格列
    const BuiltInTableColumnModel = {
      name: 'TableColumn',
      // 布局插槽 , 布局插槽可在布局中使用
      props: {
        columnName: {
          type: 'String',
          requried: false,
          editable: true,
          scope: 'public',
        },
      },
      layoutSlot: [
        {
          name: 'head',
          type: 'Component',
          requried: false,
          scope: 'public',
          dataBind: {
            columnName: '${/columnName}',
            row: '${../row}',
            column: '${../row[${/columnName}]}',
            tableData: '${../data}',
          },
          default: {
            type: 'Text',
            dataBind: {
              text: '${/columnName}',
            },
          },
        },
        {
          name: 'body',
          type: 'Component',
          requried: false,
          scope: 'public',
          dataBind: {
            columnName: '${/columnName}',
            row: '${../row}',
            column: '${../row[${/columnName}]}',
            tableData: '${../data}',
          },
          default: {
            type: 'Text',
            dataBind: {
              text: '${../row[${/columnName}]}',
            },
          },
        },
        {
          name: 'foot',
          type: 'Component',
          requried: false,
          scope: 'public',
          dataBind: {
            columnName: '${/columnName}',
            row: '${../row}',
            column: '${../row[${/columnName}]}',
            tableData: '${../data}',
          },
        },
      ],
    };

    // 表格行数据展示格
    const BuiltInTableGridModel = {
      name: 'TableGrid',
      // 布局插槽 , 布局插槽可在布局中使用
      layoutSlot: [
        {
          name: 'dataList',
          type: 'List<ComponentModel>',
          isContainer: true,
          requried: false,
          scope: 'public',
          default: true,
        },
      ],
    };

    // 表格行
    const BuiltInTableRowModel = {
      name: 'TableRow',
      // 布局插槽 , 布局插槽可在布局中使用
      layoutSlot: [
        {
          name: 'grids',
          type: 'List<TableGrid>',
          isContainer: true,
          requried: false,
          scope: 'public',
          default: true,
        },
      ],

      layout: [
        {
          loop: {
            source: '${slot/grids}',
            indexName: 'index',
            valueName: 'grid',
          },
          content: 'grid',
        },
      ],
    };

    const BuiltInTableModel = {
      // 表格, 表格支持按照 行 或者 列 方式设计, 二者互斥
      name: 'Table',
      props: [
        {
          data: {
            type: 'List<Map<String,Object>>',
            requried: false,
            editable: true,
            scope: 'public',
          },
        },
      ],
      // 布局插槽 , 布局插槽可在布局中使用
      layoutSlot: [
        {
          name: 'rows',
          type: 'List<TableRow>',
          requried: false,
          scope: 'public',
          // 设置行和设置列互斥
          reject: 'columns',
        },
        {
          name: 'columns',
          type: 'List<TableColumn>',
          requried: false,
          scope: 'public',
          // 设置行和设置列互斥
          reject: 'rows',
        },
      ],
      layout: [
        {
          // 渲染时需要采用插件进行渲染支持
          translator: 'TableTranslator',
          dataBind: {
            data: '${/data}',
          },
        },
      ],
    };

    // 用 inner 定义内联组件 , 布局插槽内部定义的唯一元素视为默认插槽
    const BuiltInTableModelXml = `
      <Table>
        <props>
          <data type="Map[String,Object][] | Object[]"  editable scope="public" />
        </props>
        <inner>
          <TableColumn>
            <props>
              <grid type="Boolean" />
              <name type="String" requried/>
              <columnName type="String" out/>
              <columnValue type="Object" out/>
            </props>
            <slots>
              <head type="ComponentModel">
                <Text value="{./columnName}"/>
              </head>
              <body type="ComponentModel">
                <Text value="{./columnValue}"/>
              </body>
              <foot type="ComponentModel"/>
            </slots>
          </TableColumn>
          <TableRow>
            <slots>
              <children type="ComponentModel[]" container requried default/>
            </slots>
          </TableRow>
        </inner>
        <slots>
          <rows type="TableRow[]" reject="columns" />
          <columns type="TableColumn[]" reject="rows" />
        </slots>
        <layout>
          <translator type='TableTranslator'/>
        </layout>
      </Table>
    `;

    /**
     * 内置模型
     */
    const BuiltInComponentModelList = [
      BuiltInBaseContainerModel,
      BuiltInTextModel,
      BuiltInImageModel,
      BuiltInTableGridModel,
      BuiltInTableColumnModel,
      BuiltInTableRowModel,
      BuiltInTableModel,
    ];

    /**
     * 组件模型
     */
    class ComponentModel {}

    /**
     * 组件模型 构建器
     */
    class ComponentModelBuilder {
      buildModelFromDocument(model) {}
    }

    /**
     * 组件 实例
     */
    class ComponentInstance {}

    /**
     * 实例构建器
     */
    class ComponentInstanceBuilder {
      buildInstanceFromModel(modelDocument) {}
    }

    /**
     * 渲染器 , 用于渲染创建后的实力
     */
    class ComponentInstanceRender {
      render(instance, element) {}
    }

    /**
     * 编辑器,提供编辑入口
     */
    class Editor {
      constructor() {
        this.modelBuilder = new ComponentModelBuilder();
        this.instanceBuilder = new ComponentInstanceBuilder();
        this.render = new ComponentInstanceRender();
        this.rootModel = null;
        this.rootInstance = null;
      }
      load(modelDocument) {
        this.rootModel =
          this.modelBuilder.buildModelFromDocument(modelDocument);
      }

      bind(element) {
        if (!this.rootModel) {
          throw new Error('the model is not loaded ');
        }
        this.rootInstance = this.instanceBuilder.buildInstanceFromModel(
          this.rootModel
        );
        this.render.render(this.rootInstance, element);
      }
    }

    const modelDocument = {
      name: 'UserProfile',
      props: {
        nickName: {
          type: 'String',
          requried: false,
          editable: true,
          scope: 'public',
        },
        id: {
          type: 'String',
          requried: false,
          editable: true,
          scope: 'public',
        },
        age: {
          type: 'String',
          requried: false,
          editable: true,
          scope: 'public',
        },
        avator: {
          type: 'String',
          requried: false,
          editable: true,
          scope: 'public',
        },
        profileProperties: {
          type: 'Map<String,Object>',
          requried: false,
          editable: true,
          scope: 'public',
        },
      },
      layout: [
        {
          content: 'Text',
          dataBind: {
            value: '${/nickName}',
          },
        },
        {
          content: 'BaseContainer',
          layoutBind: {
            children: [
              {
                content: 'Text',
                dataBind: {
                  value: '${/age}',
                },
              },
              {
                content: 'Image',
                dataBind: {
                  src: '${/avator}?id=${/id}',
                },
              },
              {
                content: 'Table',
                layoutBind: {
                  rows: {
                    loop: {
                      source: '${profileProperties}',
                      indexName: 'propName',
                      valueName: 'propValue',
                    },
                    condition: '',
                    content: 'TableRow',
                    layoutBind: {
                      rows: {
                        loop: {
                          source: '${profileProperties}',
                          indexName: 'propName',
                          valueName: 'propValue',
                        },
                        condition: '',
                        content: 'TableRow',
                        layoutBind: {
                          grids: [
                            {
                              loop: {
                                source: '${profileProperties}',
                                indexName: 'propName',
                                valueName: 'propValue',
                              },
                              condition: '',
                              content: 'TableRow',
                            },
                          ],
                        },
                      },
                    },
                  },
                },
              },
            ],
          },
        },
      ],

      demoProps: [
        {
          label: '演示数据1',
          values: {
            nickName: '张三',
            age: 18,
            avator: '/a/b/c/d/e',
            id: '0001',
          },
        },
      ],
    };

    // 历史数据模板
    const HistoryTemplate = {
      loginTime: '',
      logoutTime: '',
      ip: '',
    };

    // 如果元素只需要默布局槽可作为匿名使用 #rows 表示绑定到 rows槽中
    // loop #rows component="TableRow"  中如果 rows 插槽的类型固定则 , component="TableRow"  可以省略
    const UserProfileModelXml = `
      <UserProfile>
        <props>
          <nickName type="String" f_requried editable scope="public" />
          <id type="String" f_requried editable scope="public" />
          <age type="String" f_requried editable scope="public" />
          <avator type="String" f_requried editable scope="public" />
          <properties type="Map[String,Object]" f_requried editable scope="public" />
          <history type="History[]" f_requried editable scope="public" />
        </props>
        <layout>
          <Text value="{/nickName}" name="nickNameLabel" />
          <BaseContainer>
            <Text value="{/age}" name="ageLabel" />
            <BaseContainer>
              <children>
                <Image src="{avator}?id={/id}&test=\{\}" name="avatorImage" />  
              </children>
            </BaseContainer>
            <Table >
              <loop #rows souce="{properties}" indexName="propName" valueName="propValue" >
                <Text value="{/propName}" name="propNameLabel" />
                <Text value="{/propValue}" name="propValueLabel" />
              </loop>
            </Table>
            <Table data="{/history}">
              <columns>
                <TableColumn name="loginTime"/>
                <TableColumn name="logoutTime"/>
                <TableColumn name="ip"/>
                <TableColumn>
                  <Image src="{avator}?id={/id}&test={./row/ip}" name="avatorImage" />  
                </TableColumn>
              </columns>
            </Table>
          </BaseContainer>
        </layout>
      </UserProfile>
    `;

    const editor = new Editor();
    editor.load(modelDocument);

    let appElement = document.getElementById('app');
    editor.bind(appElement);
  </script>
</html>
